package com.tiny.tinyweb.core.dao.access;

import java.beans.PropertyDescriptor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport;

import com.tiny.tinyweb.core.dao.entity.MapEntity;
import com.tiny.tinyweb.core.exception.DaoException;
import com.tiny.tinyweb.core.utils.ConversionUtils;

/**
 * 基于{@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport}
 * 的JDBC操作辅助类，实现了基本的CRUD功能。通过跟踪表中指定字段（如更新时间）的值的变更自动支持乐观排它锁。
 *
 * @param <T> 实体类的泛型约束
 * @see{@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport}
 * @author wxiaobin
 */
public abstract class JdbcAccess<T extends MapEntity> {
    /**
     * JDBC支持对象。
     */
    private NamedParameterJdbcDaoSupport jdbcDaoSupport;

    /**
     * 设置JDBC支持对象。
     *
     * @param jdbcDaoSupport JDBC支持对象的值
     */
    public void setJdbcDaoSupport(NamedParameterJdbcDaoSupport jdbcDaoSupport) {
        this.jdbcDaoSupport = jdbcDaoSupport;
    }

    /**
     * 将结果集映射到实体的{@link org.springframework.jdbc.core.BeanPropertyRowMapper}
     * 对象。该映射器将根据表字段和实体属性的名称进行匹配，支持驼峰式和下划线分割的命名方式。</BR>
     * 例如：数据库表中的字段USER_NAME，将被映射到相应实体的userName属性中。
     *
     * @see{@link org.springframework.jdbc.core.BeanPropertyRowMapper}
     */
    private final BeanPropertyRowMapper<T> rowMapper = new BeanPropertyRowMapper<T>();

    /**
     * 设置映射器需要映射的具体的实体类的类型。
     *
     * @param mappedClass 具体的实体类的类型
     */
    public void setMappedClass(Class<T> mappedClass) {
        this.rowMapper.setMappedClass(mappedClass);
    }

    // <editor-fold defaultstate="collapsed" desc="OptimisticLock">
    /**
     * 是否自动支持乐观排他锁的标志，默认值为不支持。
     */
    private boolean autoOptimisticLock = false;

    /**
     * 设置自动支持乐观排它锁标志。
     *
     * @param autoOptimisticLock 自动支持乐观排它锁标志的值
     */
    public void setAutoOptimisticLock(boolean autoOptimisticLock) {
        this.autoOptimisticLock = autoOptimisticLock;
    }

    /**
     * 检查乐观排它锁对象是否有效，如果排它对象及排它字段被指定，则乐观锁有效。排它字段通常是更新时间。
     *
     * @param lock 乐观排它锁对象
     * @return <code>true</code>：排它对象有效；<code>false</code>：排它对象无效
     */
    private boolean validateOptimisticLock(final T lock) {
        // 排它锁对象没有指定，乐观锁无效
        if (lock == null) {
            return false;
        }

        // 设置了有效的排他字段，排它锁对象有效
        Map<String, PropertyDescriptor> columns = lock.getColumns();
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(lock);
        for (String key : columns.keySet()) {
            if (bw.getPropertyValue(columns.get(key).getName()) != null) {
                return true;
            }
        }

        // 没有设置有效的排他字段，排它锁对象无效
        return false;
    }

    /**
     * 根据运行时实体对象类型生成乐观排它锁对象。
     *
     * @param entity 更新前的实体对象，排它字段的值是检索时的状态
     * @return 添加了排他检查信息的实体对象
     */
    private T generateOptimisticLock(final T entity) {
        if (entity.getLockKeys() != null && entity.getLockKeys().size() > 0) {
            // 获得泛型的运行时类型，并创建运行时类型（具体的实体类）的实例
            Type[] args = ((ParameterizedType) this.getClass().getGenericSuperclass())
                    .getActualTypeArguments();
            @SuppressWarnings({"unchecked", "rawtypes"})
            T lockEntity = (T) BeanUtils.instantiate((Class) args[0]);
            // 将检索时的状态保存到乐观排他对象中
            for (String key : entity.getLockKeys()) {
                lockEntity.put(key, entity.get(key));
            }
            return lockEntity;
        }
        return null;
    }

    /**
     * 创建并返回一个乐观排它锁异常对象。
     *
     * @param changed 新记录的实体对象
     * @param original 原始记录的实体对象
     * @param lock 乐观排它锁检查对象
     * @return 乐观排它锁异常对象
     */
    private RuntimeException generateOptimisticException(T changed, T original, T lock) {
        return new OptimisticLockingFailureException(String.format(
                "entity[%1$s], original[%2$s], lock[%3$s]",
                changed, original, lock == null ? "null" : lock));
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="create">
    /**
     * 向表中插入一条记录，并返回受影响的记录条数。如果指定了忽略空值，那么空值字段将会被忽略。
     *
     * @param entity 需要插入表中的实体对象
     * @param ignoreNull 是否忽略空值，<code>true</code>：忽略；<code>false</code>：不忽略
     * @return 受影响的记录条数
     */
    public int jdbcCreate(final T entity, final boolean ignoreNull) {
        // 要插入的实体对象无效，直接返回
        if (entity == null) {
            return 0;
        }
        // 准备 SQL 语句和执行参数
        StringBuilder sql = new StringBuilder();
        Map<String, Object> params = new HashMap<String, Object>();
        this.prepareInsertStatement(entity, ignoreNull, sql, params);
        // 要插入的实体对象的所有字段都为空值，不执行DB操作
        if (sql.length() == 0) {
            return 0;
        }

        // 执行SQL语句向表中插入一条记录
        return this.jdbcDaoSupport.getNamedParameterJdbcTemplate().update(sql.toString(), params);
    }

    /**
     * 循环向表中插入多条记录，并返回受影响的记录条数。
     * @param entities 需要插入表中的实体对象列表
     * @param ignoreNull 是否忽略空值，<code>true</code>：忽略；<code>false</code>：不忽略
     * @return 受影响的记录条数
     * @see {@link #jdbcCreate(MapEntity, boolean)}
     */
    public int jdbcCreate(final List<T> entities, final boolean ignoreNull) {
        int result = 0;
        for (T entity : entities) {
            result += this.jdbcCreate(entity, ignoreNull);
        }
        return result;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="read">
    /**
     * 根据主键查询一条记录。如果查询到多条记录，则返回<code>null</code>。
     *
     * @param entity 包含主键值的实体对象
     * @return 主键对应记录的实体对象
     */
    public T jdbcRead(final T entity) {
        // 准备SQL语句和执行参数
        StringBuilder sql = new StringBuilder();
        Map<String, Object> params = new HashMap<String, Object>();
        this.prepareSelectStatement(entity, null, null, sql, params, true);

        // 执行数据库查询，并返回查询结果，如果查询结果为空，则返回NULL
        try {
            return this.jdbcDaoSupport.getNamedParameterJdbcTemplate().queryForObject(
                    sql.toString(), params, this.rowMapper);
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    /**
     * 查询符合条件的记录集。如果设置了排序对象列表，则根据这些排序对象构造“ORDER BY”子句对结果集进行排序。
     *
     * @param entity 包含查询条件的实体对象
     * @param sortList 排序对象列表
     * @return 符合查询条件的实体对象列表
     */
    public List<T> jdbcRead(final T entity, final List<SortHelper> sortList) {
        // 准备 SQL 语句和执行参数
        StringBuilder sql = new StringBuilder();
        Map<String, Object> params = new HashMap<String, Object>();
        this.prepareSelectStatement(entity, null, sortList, sql, params, false);

        // 执行数据库查询，并返回结果集
        return this.jdbcDaoSupport.getNamedParameterJdbcTemplate().query(
                sql.toString(), params, this.rowMapper);
    }

    /**
     * 根据指定的查询条件列表和排序列表查询符合条件的记录集。
     *
     * @param whereList 查询条件列表
     * @param sortList 排序列表
     * @return 符合查询条件的实体对象列表
     */
    public List<T> jdbcRead(final List<WhereHelper> whereList, final List<SortHelper> sortList) {
        // 获得泛型的运行时类型，并创建运行时类型（具体的实体类）的实例
        Type[] args = ((ParameterizedType) this.getClass().getGenericSuperclass())
                .getActualTypeArguments();
        @SuppressWarnings({"unchecked", "rawtypes"})
        T entity = (T) BeanUtils.instantiate((Class) args[0]);

        // 准备SQL语句和执行参数
        StringBuilder sql = new StringBuilder();
        Map<String, Object> params = new HashMap<String, Object>();
        this.prepareSelectStatement(entity, whereList, sortList, sql, params, false);

        // 执行数据库查询，并返回结果集
        return this.jdbcDaoSupport.getNamedParameterJdbcTemplate().query(
                sql.toString(), params, this.rowMapper);
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="update">
    /**
     * 根据主键更新一条记录。如果指定了忽略空值，那么空值字段将不会被更新。需要指定乐观排它锁对象。
     *
     * @param changed 包含更新值的实体对象
     * @param original 包含更新前主键值的实体对象，用来创建更新条件
     * @param lock 乐观排它锁对象，通常保存了检索时的信息
     * @param ignoreNull 是否忽略空值，<code>true</code>：忽略；<code>false</code>：不忽略
     * @return 受影响的记录条数
     */
    public int jdbcUpdate(final T changed, final T original, final T lock, final boolean ignoreNull) {
        // 准备SQL语句和执行参数
        StringBuilder sql = new StringBuilder();
        Map<String, Object> params = new HashMap<String, Object>();
        this.prepareUpdateStatement(changed, original, lock, ignoreNull, sql, params);
        // 如果没有需要更新的字段，则不执行更新操作
        if (sql.length() == 0) {
            return 0;
        }

        // 执行数据库更新操作，获得受影响的记录条数
        int result = this.jdbcDaoSupport.getNamedParameterJdbcTemplate().update(
                sql.toString(), params);

        // 检查乐观排它锁对象是否有效，如果该对象有效，并且指定条件的记录未被更新，
        // 说明要更新的记录在检索后已被更新过，抛出乐观排他异常
        if (this.validateOptimisticLock(lock) && result != 1) {
            throw this.generateOptimisticException(changed, original, lock);
        }

        // 更新成功时，返回受影响的记录条数
        return result;
    }

    /**
     * 根据主键更新一条记录。如果指定了忽略空值，那么空值字段将不会被更新。自动根据更新前实体对象创建排它锁对象。
     * 如果没有指定更新前的实体对象，那么从要从更新后实体对象中取得更新条件，同时乐观排他锁将无效。
     *
     * @param changed 包含更新值的实体对象
     * @param original 包含更新前旧值的实体对象
     * @param ignoreNull 是否忽略空值，<code>true</code>：忽略；<code>false</code>：不忽略
     * @return 受影响的记录条数
     * @see {@link #jdbcUpdate(MapEntity, MapEntity, MapEntity, boolean)}
     */
    public int jdbcUpdate(final T changed, final T original, final boolean ignoreNull) {
        return this.jdbcUpdate(changed, original == null ? changed : original,
                this.autoOptimisticLock && original != null
                        ? this.generateOptimisticLock(original) : null, ignoreNull);
    }

    /**
     * 根据指定的更新实体对象列表，循环更新多条记录。</br>
     * <ul>
     * <li>如果指定了更新前实体对象列表，那么必须和更新后实体对象列表的大小一致。</li>
     * <li>如果没有指定更新前实体对象列表，那么更新后实体对象列表的主键将被作为更新条件。</li>
     * </ul>
     *
     * @param changed 包含更新值的实体对象列表
     * @param original 包含更新前旧值的实体对象列表
     * @param ignoreNull 是否忽略空值，<code>true</code>：忽略；<code>false</code>：不忽略
     * @return 受影响的记录条数
     * @see {@link #jdbcUpdate(MapEntity, MapEntity, MapEntity, boolean)}
     */
    public int jdbcUpdate(final List<T> changed, List<T> original, final boolean ignoreNull) {
        int result = 0;
        // 如果指定了更新前实体对象列表，那么必须和更新后实体对象列表的大小一致
        if (original != null && changed.size() != original.size()) {
            return result;
        }
        // 如果没有指定更新前实体对象列表，那么更新后实体对象列表的主键将被作为更新条件
        if (original == null || original.isEmpty()) {
            original = changed;
        }
        // 循环更新
        for (int i = 0; i < changed.size(); i++) {
            result += this.jdbcUpdate(changed.get(i), original.get(i), ignoreNull);
        }
        return result;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="delete">
    /**
     * 根据主键删除一条记录。需要指定乐观排它锁检查对象。
     *
     * @param entity 包含主键值的删除条件实体对象
     * @param lock 乐观排它锁检查对象
     * @return 受影响的记录条数
     */
    public int jdbcDelete(final T entity, final T lock) {
        // 准备 SQL 语句和执行参数
        StringBuilder sql = new StringBuilder();
        Map<String, Object> params = new HashMap<String, Object>();
        this.prepareDeleteStatement(entity, lock, sql, params, true);

        // 执行数据库更新，删除主键指定的记录，并获得受影响的记录条数
        int result = this.jdbcDaoSupport.getNamedParameterJdbcTemplate().update(
                sql.toString(), params);

        // 检查乐观排它锁检查对象是否有效，如果该对象有效，并且指定主键的记录已被删除或更新，则抛出乐观排他异常
        if (this.validateOptimisticLock(lock) && result != 1) {
            throw this.generateOptimisticException(entity, entity, lock);
        }

        // 删除成功时，返回受影响的记录条数
        return result;
    }

    /**
     * 根据指定条件删除符合删除条件的记录。根据是否只处理删除条件中的主键，分两种情况：</br>
     * <ul>
     * <li>根据主键删除一条记录。自动根据删除前实体对象创建排它锁对象。</li>
     * <li>根据指定条件删除符合删除条件的记录。不进行乐观排它锁检查。</li>
     * </ul>
     *
     * @param entity 包含删除条件的实体对象
     * @param option 不指定或不为<code>true</code>时将所有非空字段都作为删除条件，否则只将主键作为删除条件
     * @return 受影响的记录条数
     */
    public int jdbcDelete(final T entity, boolean... option) {
        if (option.length > 0 && option[0]) {
            return this.jdbcDelete(entity,
                    this.autoOptimisticLock ? this.generateOptimisticLock(entity) : null);
        } else {
            // 准备SQL语句和执行参数
            StringBuilder sql = new StringBuilder();
            Map<String, Object> params = new HashMap<String, Object>();
            this.prepareDeleteStatement(entity, null, sql, params, false);

            // 执行数据库更新，删除符合条件的记录，并返回受影响的记录条数
            return this.jdbcDaoSupport.getNamedParameterJdbcTemplate().update(
                    sql.toString(), params);
        }
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="sqlStatement">
    /**
     * 根据指定的排序对象列表构造“ORDER BY”子句。
     *
     * @param columns 实体对象的属性信息
     * @param sortList 排序对象列表
     * @return “ORDER BY”子句
     */
    private String orderby(final Map<String, PropertyDescriptor> columns,
            final List<SortHelper> sortList) {
        StringBuilder orderby = new StringBuilder();
        orderby.append(" ORDER BY ");
        for (Iterator<SortHelper> it = sortList.iterator(); it.hasNext();) {
            SortHelper sortHelper = it.next();
            String column = sortHelper.getColumn().toUpperCase();
            if (!columns.containsKey(column)) {
                throw new DaoException(String.format(
                        "Column '%1$s' in sort list doesn't exist in the table.", column));
            }
            orderby.append(column).append(" ").append(sortHelper.getSqlType());
            if (it.hasNext()) {
                orderby.append(", ");
            }
        }
        return orderby.toString();
    }

    /**
     * 构造插入记录用的SQL语句。
     *
     * @param entity 要插入的实体对象
     * @param ignoreNull 是否忽略空值，<code>true</code>：忽略；<code>false</code>：不忽略
     * @param sql 保存SQL语句的{@link java.lang.StringBuilder}对象
     * @param params SQL语句的执行参数
     */
    private void prepareInsertStatement(final T entity, final boolean ignoreNull,
            StringBuilder sql, Map<String, Object> params) {
        // 加载实体对象的属性信息
        Map<String, PropertyDescriptor> columns = entity.getColumns();
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(entity);

        // 生成插入字段列表和参数化的插入值列表，准备SQL的执行参数
        StringBuilder fields = new StringBuilder();
        StringBuilder values = new StringBuilder();
        for (String key : columns.keySet()) {
            Object value = bw.getPropertyValue(columns.get(key).getName());
            // 如果指定了要忽略空值，则忽略空值字段
            if (!ignoreNull || value != null) {
                // 插入字段列表
                fields.append(key).append(", ");
                // 参数化的插入值列表
                values.append(":").append(key).append(", ");
                // SQL的执行参数
                params.put(key, value);
            }
        }
        // 检查插入字段列表是否存在
        if (fields.length() == 0) {
            return;
        }
        // 删除末尾的逗号
        if (fields.toString().endsWith(", ")) {
            fields.delete(fields.length() - 2, fields.length());
        }
        if (values.toString().endsWith(", ")) {
            values.delete(values.length() - 2, values.length());
        }

        // 构造插入记录用的SQL语句
        sql.append("INSERT INTO ");
        sql.append(entity.getTableName());
        sql.append(" (");
        sql.append(fields);
        sql.append(") VALUES (");
        sql.append(values);
        sql.append(") ");
    }

    /**
     * 构造查询用的SQL语句。
     *
     * @param entity 包含查询条件的实体对象
     * @param whereList 查询条件对象列表
     * @param sortList 排序对象列表
     * @param sql 保存SQL语句的{@link java.lang.StringBuilder}对象
     * @param params SQL语句的执行参数
     * @param withPK 只处理查询条件实体对象中的主键：<code>true</code>，否则：<code>false</code>
     */
    private void prepareSelectStatement(final T entity, final List<WhereHelper> whereList,
            final List<SortHelper> sortList, StringBuilder sql, Map<String, Object> params,
            boolean withPK) {
        // 加载实体对象的属性信息
        Map<String, PropertyDescriptor> columns = entity.getColumns();

        // 生成查询字段列表和查询条件子句，准备SQL的执行参数
        StringBuilder fields = new StringBuilder();
        StringBuilder where = new StringBuilder();
        // 查询字段列表（实体对象的属性对应的所有列）
        for (Iterator<String> it = columns.keySet().iterator(); it.hasNext();) {
            fields.append(it.next());
            if (it.hasNext()) {
                fields.append(", ");
            }
        }
        // 查询条件子句
        if (whereList != null && whereList.size() > 0) {
            // 如果指定了查询条件对象列表，则根据该列表生成查询条件子句
            for (WhereHelper whereHelper : whereList) {
                String column = whereHelper.getColumn().toUpperCase();
                // 指定的查询条件列名不属于该表，抛出列名无效的异常
                if (!columns.containsKey(column)) {
                    throw new DaoException(String.format(
                            "Column '%1$s' that will be set in SQL doesn't exist in the table.", column));
                }
                // 查询条件子句
                where.append(" AND ");
                where.append(column);
                where.append(whereHelper.getSqlType());
                // SQL的执行参数
                if (whereHelper.getType().equals(WhereHelper.Type.BETWEEN)) {
                    // BETWEEN ... AND条件
                    params.put(column + "_B", whereHelper.getValue().get(0));
                    params.put(column + "_E", whereHelper.getValue().get(1));
                } else if (whereHelper.getType().equals(WhereHelper.Type.IN)) {
                    // IN条件
                    params.put(column, whereHelper.getValue());
                } else if (whereHelper.getType().equals(WhereHelper.Type.GREATER)) {
                    // >
                    params.put(column + "_GT", whereHelper.getValue().get(0));
                } else if (whereHelper.getType().equals(WhereHelper.Type.LESS)) {
                    // <
                    params.put(column + "_LT", whereHelper.getValue().get(0));
                } else if (whereHelper.getType().equals(WhereHelper.Type.GREATER_EQUAL)) {
                    // >=
                    params.put(column + "_GE", whereHelper.getValue().get(0));
                } else if (whereHelper.getType().equals(WhereHelper.Type.LESS_EQUAL)) {
                    // >
                    params.put(column + "_LE", whereHelper.getValue().get(0));
                } else if (whereHelper.getType().equals(WhereHelper.Type.GREATER_EQUAL_TIME)) {
                    // >=
                    params.put(column + "_GET", whereHelper.getValue().get(0));
                } else if (whereHelper.getType().equals(WhereHelper.Type.LESS_EQUAL_TIME)) {
                    // >
                    params.put(column + "_LET", whereHelper.getValue().get(0));
                } else {
                 // 其他条件
                    params.put(column, whereHelper.getValue().get(0));
                }
            }
        } else {
            // 如果没有指定查询条件对象列表，则根据查询条件实体对象中指定的字段生成查询条件子句
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(entity);
            if (withPK) {
                // 只处理查询条件实体对象中的主键的情况
                List<String> pks = entity.getPrimaryKeys();
                if (pks != null && !pks.isEmpty()) {
                    for (Iterator<String> it = pks.iterator(); it.hasNext();) {
                        String pk = ConversionUtils.underscoreName(it.next());
                        // 查询条件子句
                        where.append(" AND ").append(pk).append(" = :").append(pk);
                        // SQL的执行参数
                        params.put(pk, bw.getPropertyValue(columns.get(pk).getName()));
                    }
                }
            } else {
                // 处理查询条件实体对象中所有指定了值的字段的情况
                for (Iterator<String> it = columns.keySet().iterator(); it.hasNext();) {
                    String key = it.next();
                    Object value = bw.getPropertyValue(columns.get(key).getName());
                    if (value != null) {
                        // 查询条件子句
                        where.append(" AND ").append(key).append(" = :").append(key);
                        // SQL的执行参数
                        params.put(key, value);
                    }
                }
            }
        }

        // 构造查询用的SQL语句
        sql.append("SELECT ");
        sql.append(fields);
        sql.append(" FROM ");
        sql.append(entity.getTableName());
        if (where.length() > 0) {
            sql.append(" WHERE 1 = 1 ");
            sql.append(where);
        }
        // 处理排序子句
        if (sortList != null && sortList.size() > 0) {
            sql.append(orderby(columns, sortList));
        }
    }

    /**
     * 构造更新记录用的SQL语句。
     *
     * @param changed 包含更新信息的实体对象
     * @param original 包含原始记录信息的实体对象
     * @param lock 乐观排它锁检查对象
     * @param 是否忽略空值，<code>true</code>：忽略；<code>false</code>：不忽略
     * @param sql 保存SQL语句的{@link java.lang.StringBuilder}对象
     * @param params SQL语句的执行参数
     */
    private void prepareUpdateStatement(final T changed, final T original, final T lock,
            final boolean ignoreNull, StringBuilder sql, Map<String, Object> params) {
        // 加载实体对象的属性信息
        Map<String, PropertyDescriptor> columns = changed.getColumns();
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(changed);

        // 生成更新字段列表和更新条件子句，准备SQL的执行参数
        StringBuilder fields = new StringBuilder();
        StringBuilder where = new StringBuilder();
        for (String key : columns.keySet()) {
            Object value = bw.getPropertyValue(columns.get(key).getName());
            // 如果指定了要忽略空值，则忽略空值字段
            if (!ignoreNull || value != null) {
                // 更新字段列表
                fields.append(key).append(" = :").append(key).append(", ");
                // SQL的执行参数
                params.put(key, value);
            }
        }
        // 检查更新字段列表是否存在
        if (fields.length() == 0) {
            return;
        }
        // 更新条件子句，为了区分乐观锁条件，添加“_W”后缀
        List<String> pks = changed.getPrimaryKeys();
        if (pks != null && !pks.isEmpty()) {
            bw = PropertyAccessorFactory.forBeanPropertyAccess(original);
            for (Iterator<String> it = pks.iterator(); it.hasNext();) {
                String pk = ConversionUtils.underscoreName(it.next());
                where.append(" AND ").append(pk).append(" = :").append(pk).append("_W");
                // SQL的执行参数
                params.put(pk + "_W", bw.getPropertyValue(columns.get(pk).getName()));
            }
        }
        // 把乐观排它锁检查对象中指定了值的字段添加到更新条件中
        if (lock != null) {
            bw = PropertyAccessorFactory.forBeanPropertyAccess(lock);
            for (String key : columns.keySet()) {
                // 如果乐观排他检查字段的值被更新过，则满足这些更新条件的记录不存在
                Object value = bw.getPropertyValue(columns.get(key).getName());
                if (value != null) {
                    // 更新条件子句，为了区分更新条件，添加“_L”后缀
                    where.append(" AND ").append(key).append(" = :").append(key).append("_L");
                    // SQL的执行参数
                    params.put(key + "_L", bw.getPropertyValue(columns.get(key).getName()));
                }
            }
        }

        // 删除末尾的逗号
        if (fields.toString().endsWith(", ")) {
            fields.delete(fields.length() - 2, fields.length());
        }

        // 构造更新记录用的SQL语句
        sql.append("UPDATE ");
        sql.append(changed.getTableName());
        sql.append(" SET ");
        sql.append(fields);
        sql.append(" WHERE 1 = 1 ");
        sql.append(where);
    }

    /**
     * 构造删除记录用的SQL语句。
     *
     * @param entity 包含删除条件的实体对象
     * @param lock 乐观排它锁检查对象
     * @param sql 保存SQL语句的{@link java.lang.StringBuilder}对象
     * @param params SQL语句的执行参数
     * @param withPK 只处理删除条件实体对象中的主键：true，否则：false
     */
    private void prepareDeleteStatement(final T entity, final T lock, StringBuilder sql,
            Map<String, Object> params, boolean withPK) {
        // 加载实体对象的属性信息
        Map<String, PropertyDescriptor> columns = entity.getColumns();
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(entity);

        // 生成删除条件子句，准备SQL的执行参数
        StringBuilder where = new StringBuilder();
        if (withPK) {
            // 只处理删除条件实体对象中的主键的情况
            List<String> pks = entity.getPrimaryKeys();
            if (pks != null && !pks.isEmpty()) {
                for (Iterator<String> it = pks.iterator(); it.hasNext();) {
                    String pk = ConversionUtils.underscoreName(it.next());
                    // 删除条件子句
                    where.append(" AND ").append(pk).append(" = :").append(pk);
                    // SQL的执行参数
                    params.put(pk, bw.getPropertyValue(columns.get(pk).getName()));
                }
            }
        } else {
            // 处理删除条件实体对象中所有指定了值的字段的情况
            for (String key : columns.keySet()) {
                Object value = bw.getPropertyValue(columns.get(key).getName());
                if (value != null) {
                    // 删除条件子句
                    where.append(" AND ").append(key).append(" = :").append(key);
                    // SQL的执行参数
                    params.put(key, value);
                }
            }
        }

        // 把乐观排它锁检查对象中指定了值的字段添加到删除条件中
        if (lock != null) {
            bw = PropertyAccessorFactory.forBeanPropertyAccess(lock);
            for (String key : columns.keySet()) {
                // 如果乐观排他检查字段的值被更新过，则满足这些删除条件的记录不存在
                Object value = bw.getPropertyValue(columns.get(key).getName());
                if (value != null) {
                    // 删除条件子句
                    where.append(" AND ").append(key).append(" = :").append(key).append("_L");
                    // SQL的执行参数
                    params.put(key + "_L", bw.getPropertyValue(columns.get(key).getName()));
                }
            }
        }

        // 构造删除记录用的SQL语句
        sql.append("DELETE FROM ");
        sql.append(entity.getTableName());
        sql.append(" WHERE 1 = 1 ");
        sql.append(where);
    }
    // </editor-fold>
}
